/********************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
File name:   AIActor.cpp
$Id$
Description: implementation of the CAIActor class.

-------------------------------------------------------------------------
History:
14:12:2006 -  Created by Kirill Bulatsev


*********************************************************************/
//////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "AIRecorder.h"
#include "AIActor.h"
#include "PipeUser.h"
#include "AIVehicle.h"
#include "GoalOp.h"
#include "SelectionTree/SelectionTreeManager.h"
#include "TargetSelection/TargetTrackManager.h"
#include "Navigation/NavigationSystem/NavigationSystem.h"
#include "Group/GroupManager.h"
#include "Factions/FactionMap.h"
#include "crc32.h"

#include <VisionMapTypes.h>
#include "SelectionTree/SelectionTreeDebugger.h"

#define GET_READY_TO_CHANGE_BEHAVIOR_SIGNAL	"OnBehaviorChangeRequest"

//#pragma optimize("", off)
//#pragma inline_depth(0)

static const float UNINITIALIZED_COS_CACHE = 2.0f;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#define _ser_value_(val) ser.Value(#val, val)


#pragma warning (disable : 4355)
CAIActor::CAIActor() :
	m_bCheckedBody(true),
#ifdef CRYAISYSTEM_DEBUG
	m_healthHistory(0),
	m_pRecorderUnit(NULL),
#endif
	m_lightLevel(AILL_LIGHT),
	m_usingCombatLight(false),
	m_perceptionDisabled(0),
	m_cachedWaterOcclusionValue(0.0f),
	m_vLastFullUpdatePos(ZERO),
	m_observer(false),
	m_bCloseContact(false),
	m_FOVPrimaryCos(UNINITIALIZED_COS_CACHE),
	m_FOVSecondaryCos(UNINITIALIZED_COS_CACHE),
	m_territoryShape(0),
	m_lastBodyDir(ZERO),
	m_bodyTurningSpeed(0),
	m_stimulusStartTime(-100.f),
	m_activeCoordinationCount(0),
	m_navigationTypeID(0)
{
	_fastcast_CAIActor = true;

	AILogComment("CAIActor (%p)", this);
}
#pragma warning (default : 4355)

CAIActor::~CAIActor()
{
	AILogComment("~CAIActor  %s (%p)", GetName(), this);

	gAIEnv.pGroupManager->RemoveGroupMember(GetGroupId(), GetAIObjectID());

	CAISystem* pAISystem = GetAISystem();

	SetObserver(false);

#ifdef CRYAISYSTEM_DEBUG
	delete m_healthHistory;
#endif

	m_State.ClearSignals();

	pAISystem->NotifyEnableState(this, false);
	pAISystem->UnregisterAIActor(StaticCast<CAIActor>(GetSelfReference()));
}

void CAIActor::SetName(const char* szName)
{
	CAIObject::SetName(szName);

#ifdef CRYAISYSTEM_DEBUG
	if (m_pRecorderUnit)
	{
		m_pRecorderUnit->SetName(szName);
	}
#endif //CRYAISYSTEM_DEBUG
}

void CAIActor::SetBehaviorVariable(const char* variableName, bool value)
{
	if (m_behaviorSelectionTree.get())
	{
		SelectionVariableID variableID = 
			m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().GetVariableID(variableName);
		assert(m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().IsDeclared(variableID));
		m_behaviorSelectionVariables->SetVariable(variableID, value);
	}
}

bool CAIActor::GetBehaviorVariable(const char* variableName) const
{
	bool value = false;

	if (m_behaviorSelectionTree.get())
	{
		SelectionVariableID variableID = 
			m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().GetVariableID(variableName);
	
		m_behaviorSelectionVariables->GetVariable(variableID, &value);
	}
	
	return value;
}


SelectionTree* CAIActor::GetBehaviorSelectionTree() const
{
	return m_behaviorSelectionTree.get();
}

SelectionVariables* CAIActor::GetBehaviorSelectionVariables() const
{
	return m_behaviorSelectionVariables.get();
}

void CAIActor::ResetBehaviorSelectionTree(EObjectResetType type)
{
	bool bRemoveBehaviorSelectionTree = (type == AIOBJRESET_SHUTDOWN);
	IAIActorProxy* proxy = GetProxy();

	if (!bRemoveBehaviorSelectionTree && proxy)
	{
		const char* behaviorSelectionTreeName = proxy->GetBehaviorSelectionTreeName();

		bool treeChanged = ((behaviorSelectionTreeName && !m_behaviorSelectionTree.get()) ||
			(behaviorSelectionTreeName!=0 && stricmp(m_behaviorSelectionTree->GetTemplate().GetName(), behaviorSelectionTreeName)));

		if (treeChanged)
		{
			SelectionTreeTemplateID templateID = gAIEnv.pSelectionTreeManager->GetTreeTemplateID(behaviorSelectionTreeName);

			if (gAIEnv.pSelectionTreeManager->HasTreeTemplate(templateID))
			{
				const SelectionTreeTemplate& treeTemplate = gAIEnv.pSelectionTreeManager->GetTreeTemplate(templateID);
				if (treeTemplate.Valid())
				{
					m_behaviorSelectionTree.reset(new SelectionTree(treeTemplate.GetSelectionTree()));
					m_behaviorSelectionVariables.reset(new SelectionVariables(treeTemplate.GetVariableDeclarations().GetDefaults()));
					m_behaviorSelectionVariables->ResetChanged(true);
				}
			}
			else
			{
				bRemoveBehaviorSelectionTree = true;
			}
		}
	}

	if (bRemoveBehaviorSelectionTree)
	{
		m_behaviorSelectionTree.reset();
		m_behaviorSelectionVariables.reset();
	}
}

bool CAIActor::ProcessBehaviorSelectionTreeSignal(const char* signalName, uint32 signalCRC)
{
	FUNCTION_PROFILER( gEnv->pSystem, PROFILE_AI );

	if (m_behaviorSelectionVariables.get())
	{
		const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate();
		return treeTemplate.GetSignalVariables().ProcessSignal(signalName, signalCRC, *m_behaviorSelectionVariables);
	}

	return false;
}

bool CAIActor::UpdateBehaviorSelectionTree()
{
	FUNCTION_PROFILER( gEnv->pSystem, PROFILE_AI );

	if (m_behaviorSelectionTree.get())
	{
		if (m_behaviorSelectionVariables.get() && m_behaviorSelectionVariables->Changed())
		{
			const char* behaviorName = "";

			SelectionNodeID currentNodeID = m_behaviorSelectionTree->GetCurrentNodeID();
			BST_DEBUG_START_EVAL(this, *m_behaviorSelectionTree.get(), *m_behaviorSelectionVariables.get());
			SelectionNodeID selectedNodeID = m_behaviorSelectionTree->Evaluate(*m_behaviorSelectionVariables.get());
			BST_DEBUG_END_EVAL(this, selectedNodeID);
			if (selectedNodeID)
			{
				m_behaviorSelectionVariables->ResetChanged();

				if (currentNodeID == selectedNodeID)
					return false;

				const SelectionTreeNode& node = m_behaviorSelectionTree->GetNode(selectedNodeID);
				behaviorName = node.GetName();

				const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate();
				if (const char* translatedName = treeTemplate.GetTranslator().GetTranslation(selectedNodeID))
					behaviorName = translatedName;
			}

			IAIActorProxy *pProxy = GetProxy();
			assert(pProxy);
			if (pProxy)
				pProxy->SetBehaviour(behaviorName);

			return true;
		}
	}

	return false;
}

void CAIActor::DebugDrawBehaviorSelectionTree()
{
	if (m_behaviorSelectionVariables.get())
	{
		const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate();
		m_behaviorSelectionVariables->DebugDraw(true, treeTemplate.GetVariableDeclarations());
	}

	if (m_behaviorSelectionTree.get())
		m_behaviorSelectionTree->DebugDraw();
}

const SAIBodyInfo& CAIActor::QueryBodyInfo()
{
	m_proxy->QueryBodyInfo(m_bodyInfo);
	return m_bodyInfo;
}

const SAIBodyInfo& CAIActor::GetBodyInfo() const
{
	return m_bodyInfo;
}

void CAIActor::SetPos(const Vec3& pos, const Vec3& dirFwrd)
{
	gAIEnv.pActorLookUp->Prepare(0);

	Vec3 position(pos);
	Vec3 vEyeDir(dirFwrd);

	if (IAIActorProxy* pProxy = GetProxy())
	{
		SAIBodyInfo bodyInfo;
		pProxy->QueryBodyInfo(bodyInfo);

		assert(bodyInfo.vEyeDir.IsValid());
		assert(bodyInfo.vEyePos.IsValid());
		assert(bodyInfo.vFireDir.IsValid());
		assert(bodyInfo.vFirePos.IsValid());

		position = bodyInfo.vEyePos;
		vEyeDir = bodyInfo.GetEyeDir();
		assert(vEyeDir.IsUnit());

		SetViewDir(vEyeDir);
		SetBodyDir(bodyInfo.GetBodyDir());

		SetFirePos(bodyInfo.vFirePos);
		SetFireDir(bodyInfo.vFireDir);
		SetMoveDir(bodyInfo.vMoveDir);
		SetEntityDir(bodyInfo.GetBodyDir());

		assert(bodyInfo.vFireDir.IsUnit());
		assert(bodyInfo.vMoveDir.IsUnit() || bodyInfo.vMoveDir.IsZero());
		assert(bodyInfo.vEntityDir.IsUnit() || bodyInfo.vEntityDir.IsZero());
	}

	CAIObject::SetPos(position, vEyeDir); // can set something else than passed position

	if (m_pFormation)
		m_pFormation->Update();

	if (m_observer)
	{
		ObserverParams observerParams;
		observerParams.eyePos = position;
		observerParams.eyeDir = vEyeDir;

		gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedPosition | eChangedOrientation);
	}

	gAIEnv.pActorLookUp->UpdatePosition(this, position);
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::Reset(EObjectResetType type)
{
	CAIObject::Reset(type);

	m_bCheckedBody = true;
	
	CAISystem* pAISystem = GetAISystem();

	m_State.FullReset();

	// TODO : (MATT) Hack - move into the method {2007/10/30:21:01:16}
	m_State.eLookStyle = LOOKSTYLE_DEFAULT;

	ReleaseFormation();

	if (!m_proxy)
	{
		AILogComment("CAIActor(%p) Creating AIActorProxy", this);
		m_proxy.reset(pAISystem->GetActorProxyFactory()->CreateActorProxy(GetEntityID()));
		gAIEnv.pActorLookUp->UpdateProxy(this);
	}

	m_proxy->Reset(type);

	m_bEnabled = true;
	m_bUpdatedOnce = false;

#ifdef CRYAISYSTEM_DEBUG
	if (m_healthHistory) 
		m_healthHistory->Reset();
#endif

	// synch self with owner entity if there is one
	IEntity* pEntity(GetEntity());
	if (pEntity)
	{
		m_bEnabled = pEntity->IsActive();
		SetPos(pEntity->GetPos());
	}

	m_lightLevel = AILL_LIGHT;
	m_usingCombatLight = false;
	assert(m_perceptionDisabled == 0);
	m_perceptionDisabled = 0;

	m_cachedWaterOcclusionValue = 0.0f;

	m_vLastFullUpdatePos.zero();

	m_detectionMax.Reset();
	m_detectionSnapshot.Reset();

	m_probableTargets.clear();

	m_blackBoard.Clear();
	m_blackBoard.GetForScript()->SetValue("Owner", this->GetName());

	m_perceptionHandlerModifiers.clear();

	ResetPersonallyHostiles();
	ResetBehaviorSelectionTree(type);

	const char* navigationTypeName = m_proxy->GetNavigationTypeName();
	if (navigationTypeName && *navigationTypeName)
	{
		if (NavigationAgentTypeID id = gAIEnv.pNavigationSystem->GetAgentTypeID(navigationTypeName))
		{
			m_navigationTypeID = id;
		}
	}

	pAISystem->NotifyEnableState(this, m_bEnabled && (type == AIOBJRESET_INIT));

	// Clear the W/T names we use for lookup
	m_territoryShape = 0;
	m_territoryShapeName.clear();

	m_lastBodyDir.zero();
	m_bodyTurningSpeed = 0;

	if (GetType() != AIOBJECT_PLAYER)
	{
		SetObserver(type == AIOBJRESET_INIT);
		SetObservable(type == AIOBJRESET_INIT);
	}
	
	m_bCloseContact = false;
	m_stimulusStartTime = -100.f;

	m_bodyInfo = SAIBodyInfo();

	m_activeCoordinationCount = 0;
}

void CAIActor::EnablePerception(bool enable)
{
	if (enable)
		--m_perceptionDisabled;
	else
		++m_perceptionDisabled;

	assert(m_perceptionDisabled >= 0);	// Below zero? More disables then enables!
	assert(m_perceptionDisabled < 16); // Just a little sanity check
}

bool CAIActor::IsPerceptionEnabled() const
{
	return m_perceptionDisabled <= 0;
}

void CAIActor::ResetPerception()
{
	m_probableTargets.clear();
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::ParseParameters(const AIObjectParams& params, bool bParseMovementParams)
{
	SetParameters(params.m_sParamStruct);

	if (bParseMovementParams)
		m_movementAbility = params.m_moveAbility;
	
	GetAISystem()->NotifyEnableState(this, m_bEnabled);
}

//====================================================================
// CAIObject::OnObjectRemoved
//====================================================================
void CAIActor::OnObjectRemoved(CAIObject* pObject )
{
	CAIObject::OnObjectRemoved(pObject);

	// make sure no pending signal left from removed AIObjects
	if (!m_State.vSignals.empty())
	{
		if (EntityId removedEntityID = pObject->GetEntityID())
		{
			DynArray<AISIGNAL>::iterator it = m_State.vSignals.begin();
			DynArray<AISIGNAL>::iterator end = m_State.vSignals.end();

			for ( ; it != end; )
			{
				AISIGNAL& curSignal = *it;
				
				if (curSignal.senderID == removedEntityID)
				{
					delete static_cast<AISignalExtraData*>(curSignal.pEData);
					it = m_State.vSignals.erase(it);
					end = m_State.vSignals.end();
				}
				else
					++it;
			}
		}
	}

	for (unsigned i = 0; i < m_probableTargets.size(); )
	{
		if (m_probableTargets[i] == pObject)
		{
			m_probableTargets[i] = m_probableTargets.back();
			m_probableTargets.pop_back();
		}
		else
			++i;
	}

	RemovePersonallyHostile(pObject->GetAIObjectID());
}


//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::Update(EObjectUpdate type)
{
	IAIActorProxy* pAIActorProxy = GetProxy();

	if (!CastToCPipeUser())
	{
		if (!IsEnabled())
		{
			AIWarning("CAIActor::Update: Trying to update disabled AI Actor: %s", GetName());
			AIAssert(0);
			return;
		}

		// There should never be AI Actors without proxies.
		if (!pAIActorProxy)
		{
			AIWarning("CAIActor::Update: AI Actor does not have proxy: %s", GetName());
			AIAssert(0);
			return;
		}
		// There should never be AI Actors without physics.
		if (!GetPhysics())
		{
			AIWarning("CAIActor::Update: AI Actor does not have physics: %s", GetName());
			AIAssert(0);
			return;
		}
		// dead AI Actors should never be updated
		if (pAIActorProxy->IsDead())
		{
			AIWarning("CAIActor::Update: Trying to update dead AI Actor: %s ", GetName());
			AIAssert(0);
			return;
		}
	}

	QueryBodyInfo();

	UpdateBehaviorSelectionTree();
	UpdateCloakScale();

	CAISystem* pAISystem = GetAISystem();

	// Determine if position has changed
	const Vec3& vPos = GetPos();
	if (type == AIUPDATE_FULL)
	{
		if (!IsEquivalent(m_vLastFullUpdatePos, vPos, 1.f))
		{
			// Recalculate the water occlusion at the new point
			m_cachedWaterOcclusionValue = pAISystem->GetWaterOcclusionValue(vPos);
			
			m_vLastFullUpdatePos = vPos;
		}

		// update close contact info
		if (m_bCloseContact && ((pAISystem->GetFrameStartTime() - m_CloseContactTime).GetMilliSecondsAsInt64() > 1500))
		{
			m_bCloseContact = false;
		}
	}

	const float dt = pAISystem->GetFrameDeltaTime();
	if (dt > 0.f)
	{
		// Update body angle and body turn speed
		float turnAngle = Ang3::CreateRadZ(m_lastBodyDir, GetEntityDir());
		m_bodyTurningSpeed = turnAngle / dt;
	}
	else
	{
		m_bodyTurningSpeed = 0;
	}

	m_lastBodyDir = GetEntityDir();

	if (!CastToCPipeUser())
	{
		if (type == AIUPDATE_FULL)
		{
			m_lightLevel = pAISystem->GetLightManager()->GetLightLevelAt(GetPos(), this, &m_usingCombatLight);
		}

		// make sure to update direction when entity is not moved
		const SAIBodyInfo& bodyInfo = GetBodyInfo();
		SetPos(bodyInfo.vEyePos);
		SetEntityDir(bodyInfo.vEntityDir);
		SetBodyDir(bodyInfo.GetBodyDir());

		// AI Actor goto stuff
		if (!m_State.vMoveTarget.IsZero())
		{
			Vec3 vToMoveTarget = m_State.vMoveTarget - GetPos();
			if (!m_movementAbility.b3DMove)
			{
				vToMoveTarget.z = 0.f;
			}
			if (vToMoveTarget.GetLengthSquared() < sqr(m_movementAbility.pathRadius))
			{
				ResetLookAt();
				SetBodyTargetDir(bodyInfo.vEntityDir);
			}
			else
			{
				SetBodyTargetDir(vToMoveTarget.GetNormalized());
			}
		}
		// End of AI Actor goto stuff
		
		SetMoveDir(bodyInfo.vMoveDir);
		m_State.vMoveDir = bodyInfo.vMoveDir;

		SetViewDir(bodyInfo.GetEyeDir());

		CAIObject* pAttTarget = m_refAttentionTarget.GetAIObject();
		if (pAttTarget && pAttTarget->IsEnabled())
		{
			if (CanSee(pAttTarget->GetVisionID()))
			{
				m_State.eTargetType = AITARGET_VISUAL;
				m_State.nTargetType = pAttTarget->GetType();
				m_State.bTargetEnabled = true;
			}
			else
			{
				switch (m_State.eTargetType)
				{
				case AITARGET_VISUAL:
					m_State.eTargetThreat = AITHREAT_AGGRESSIVE;
					m_State.eTargetType = AITARGET_MEMORY;
					m_State.nTargetType = pAttTarget->GetType();
					m_State.bTargetEnabled = true;
					m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds();
					break;

				case AITARGET_MEMORY:
				case AITARGET_SOUND:
					if (GetAISystem()->GetFrameStartTimeSeconds() - m_stimulusStartTime >= 5.f)
					{
						m_State.nTargetType = -1;
						m_State.bTargetEnabled = false;
						m_State.eTargetThreat = AITHREAT_NONE;
						m_State.eTargetType = AITARGET_NONE;

						SetAttentionTarget(NILREF);
					}
					break;
				}
			}
		}
		else
		{
			m_State.nTargetType = -1;
			m_State.bTargetEnabled = false;
			m_State.eTargetThreat = AITHREAT_NONE;
			m_State.eTargetType = AITARGET_NONE;

			SetAttentionTarget(NILREF);
		}
	}
}

void CAIActor::UpdateProxy(EObjectUpdate type)
{
	IAIActorProxy* pAIActorProxy = GetProxy();
	
	SetMoveDir(m_State.vMoveDir);

	// Always update the AI proxy, also during dry updates. The Animation system
	// needs correct and constantly updated predictions to correctly set animation
	// parameters.
	// (MATT) Try avoiding UpdateMind, which triggers script, signal and behaviour code, if only a dry update {2009/12/06}
	assert(pAIActorProxy);
	if(pAIActorProxy)
		pAIActorProxy->Update(m_State, (type == AIUPDATE_FULL));
}


//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::UpdateCloakScale()
{
	float	delta(m_Parameters.m_fCloakScaleTarget - m_Parameters.m_fCloakScale);
	if( fabsf(delta)>.05f ) 
	{
		float	scaleStep(GetAISystem()->GetFrameDeltaTime()*.5f);
		if(delta>0.f)
			m_Parameters.m_fCloakScale = scaleStep<delta ? (m_Parameters.m_fCloakScale + scaleStep) : m_Parameters.m_fCloakScaleTarget;
		else
			m_Parameters.m_fCloakScale = scaleStep<-delta ? (m_Parameters.m_fCloakScale - scaleStep) : m_Parameters.m_fCloakScaleTarget;
	}
	else
		m_Parameters.m_fCloakScale = m_Parameters.m_fCloakScaleTarget;
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::UpdateDisabled(EObjectUpdate type)
{
	FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

	// (MATT) I'm assuming that AIActor should always have a proxy, or this could be bad for performance {2009/04/03}
	IAIActorProxy* pProxy = GetProxy();
	if (pProxy)
		pProxy->CheckUpdateStatus();
}

//===================================================================
// SetNavNodes
//===================================================================
void CAIActor::UpdateDamageParts(DamagePartVector& parts)
{
	IAIActorProxy* pProxy = GetProxy();
	if (!pProxy)
		return;

	IPhysicalEntity* phys = pProxy->GetPhysics(true);
	if (!phys)
		return;

	bool queryDamageValues = true;

	static ISurfaceTypeManager* pSurfaceMan = gEnv->p3DEngine->GetMaterialManager()->GetSurfaceTypeManager();

	pe_status_nparts statusNParts;
	int nParts = phys->GetStatus(&statusNParts);

	if ((int)parts.size() != nParts)
	{
		parts.resize(nParts);
		queryDamageValues = true;
	}

	// The global damage table
	SmartScriptTable pSinglePlayerTable;
	SmartScriptTable pDamageTable;
	if (queryDamageValues)
	{
		gEnv->pScriptSystem->GetGlobalValue("SinglePlayer", pSinglePlayerTable);
		if (pSinglePlayerTable.GetPtr())
		{
			if (GetType() == AIOBJECT_PLAYER)
				pSinglePlayerTable->GetValue("DamageAIToPlayer", pDamageTable);
			else
				pSinglePlayerTable->GetValue("DamageAIToAI", pDamageTable);
		}
		if (!pDamageTable.GetPtr())
			queryDamageValues = false;
	}

	pe_status_pos statusPos;
	pe_params_part paramsPart;
	for (statusPos.ipart = 0, paramsPart.ipart = 0 ; statusPos.ipart < nParts ; ++statusPos.ipart, ++paramsPart.ipart)
	{
		if (!phys->GetParams(&paramsPart) || !phys->GetStatus(&statusPos))
			continue;

		primitives::box box;
		statusPos.pGeomProxy->GetBBox(&box);

		box.center *= statusPos.scale;
		box.size *= statusPos.scale;

		parts[statusPos.ipart].pos = statusPos.pos + statusPos.q * box.center;
		parts[statusPos.ipart].volume = (box.size.x * 2) * (box.size.y * 2) * (box.size.z * 2);

		if (queryDamageValues)
		{
			const	char*	matName = 0;
			phys_geometry* pGeom = paramsPart.pPhysGeomProxy ? paramsPart.pPhysGeomProxy : paramsPart.pPhysGeom;
			if (pGeom->surface_idx >= 0 &&  pGeom->surface_idx < paramsPart.nMats)
			{
				matName = pSurfaceMan->GetSurfaceType(pGeom->pMatMapping[pGeom->surface_idx])->GetName();      
				parts[statusPos.ipart].surfaceIdx = pGeom->surface_idx;
			}
			else
			{
				parts[statusPos.ipart].surfaceIdx = -1;
			}
			float	damage = 0.0f;
			// The this is a bit dodgy, but the keys in the damage table exclude the 'mat_' part of the material name.
			if (pDamageTable.GetPtr() && matName && strlen(matName) > 4)
				pDamageTable->GetValue(matName + 4, damage);
			parts[statusPos.ipart].damageMult = damage;
		}
	}
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::NotifySignalReceived(const char* szText, IAISignalExtraData* pData, uint32 crcCode)
{
	// always process signals sent only to notify wait goal operation
	if (!crcCode)
		crcCode = gAIEnv.SignalCRCs.m_crcGen->GetCRC32(szText);

	ProcessBehaviorSelectionTreeSignal(szText, crcCode);
}

// nSignalID = 10 allow duplicating signals
// nSignalID = 9 use the signal only to notify wait goal operations
//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::SetSignal(int nSignalID, const char* szText, IEntity* pSender, IAISignalExtraData* pData, uint32 crcCode)
{
	CCCPOINT(SetSignal);

	// Ensure we delete the pData object if we early out
	struct DeleteBeforeReturning
	{
		IAISignalExtraData** _p;
		DeleteBeforeReturning( IAISignalExtraData** p ) : _p(p) {}
		~DeleteBeforeReturning()
		{ 
			if ( *_p )
				GetAISystem()->FreeSignalExtraData( (AISignalExtraData*)*_p );
		}
	} autoDelete( &pData );

#ifdef _DEBUG
	if(strlen(szText)>=AISIGNAL::SIGNAL_STRING_LENGTH-1)
	{
		AIWarning("####>CAIObject::SetSignal SIGNAL STRING IS TOO LONG for <%s> :: %s  sz-> %d", GetName(),szText,(int32)strlen(szText));
		//		AILogEvent("####>CAIObject::SetSignal <%s> :: %s  sz-> %d",m_sName.c_str(),szText,strlen(szText));
	}
#endif // _DEBUG

	// always process signals sent only to notify wait goal operation
	if (!crcCode)
		crcCode = gAIEnv.SignalCRCs.m_crcGen->GetCRC32(szText);

	// (MATT) This is the only place that the CRCs are used and their implementation is very clumsy {2008/08/09}
	if ( nSignalID != AISIGNAL_NOTIFY_ONLY )
	{
		if( nSignalID != AISIGNAL_ALLOW_DUPLICATES)
		{
			DynArray<AISIGNAL>::iterator ai;
			for (ai=m_State.vSignals.begin();ai!=m_State.vSignals.end();++ai)
			{
				//	if ((*ai).strText == szText)
				//if (!stricmp((*ai).strText,szText))

#ifdef _DEBUG
				if (	!stricmp((*ai).strText,szText) &&	!(*ai).Compare(crcCode) )
				{
					AIWarning("Hash values are different, but strings are identical! %s - %s ",(*ai).strText, szText);
					crcCode = gAIEnv.SignalCRCs.m_crcGen->GetCRC32((*ai).strText);
					return;
				}

				if(stricmp((*ai).strText,szText) && (*ai).Compare(crcCode))
				{
					AIWarning("Please report to alexey@crytek.de! Hash values are identical, but strings are different! %s - %s ",(*ai).strText, szText);
				}
#endif // _DEBUG

				if ((*ai).Compare(crcCode))
					return;
			}
		}

		if (!m_bEnabled && nSignalID != AISIGNAL_INCLUDE_DISABLED)
		{
			// (Kevin) This seems like an odd assumption to be making. INCLUDE_DISABLED needs to be a bit or a separate passed-in value.
			//	WarFace compatibility cannot have duplicate signals sent to disabled AI. (08/14/2009)
			if (gAIEnv.configuration.eCompatibilityMode == ECCM_WARFACE || nSignalID != AISIGNAL_ALLOW_DUPLICATES)
			{
				AILogComment("AIActor %x %s dropped signal \'%s\' due to being disabled",(unsigned)(this),GetName(),szText);
				return;
			}
		}
	}

	AISIGNAL signal;
	signal.nSignal = nSignalID;

	//signal.strText = szText;
	strncpy(signal.strText,szText,AISIGNAL::SIGNAL_STRING_LENGTH);
	//	strcpy(signal.strText,szText);
	signal.m_nCrcText = crcCode;
	signal.senderID = pSender ? pSender->GetId() : 0;
	signal.pEData = pData;

	if(nSignalID != AISIGNAL_RECEIVED_PREV_UPDATE)// received last update 
	{
#if defined(CRYAISYSTEM_DEBUG) || defined(CRYAISYSTEM_LOG_AI_ACTORS)
		SAIDebugEventData recorderEventData(szText);
#endif

#ifdef CRYAISYSTEM_DEBUG
		RecordAIDebugEvent(eAIDE_SignalReceived, &recorderEventData);
#endif

#ifdef CRYAISYSTEM_LOG_AI_ACTORS
		GetAISystem()->LogAIActorDebugEvent(this, eAIDE_SignalReceived, szText);
#endif
		
		// (MATT) I suspect that the data part is not used at all {2008/08/09}
		NotifySignalReceived( szText, signal.pEData, crcCode );
	}

	// don't let notify signals enter the queue
	if ( nSignalID == AISIGNAL_NOTIFY_ONLY )
		return;

	// If all our early-outs passed and it wasn't just a "notify" then actually enter the signal into the stack!
	pData = NULL; // set to NULL to prevent autodeletion of pData on return

	// need to make sure constructor signal is always at the back - to be processed first
	if(!m_State.vSignals.empty())
	{
		AISIGNAL backSignal(m_State.vSignals.back());
	
		if (!stricmp("Constructor", backSignal.strText) )
		{
			m_State.vSignals.pop_back();
			m_State.vSignals.push_back(signal);
			m_State.vSignals.push_back(backSignal);
		}
		else
			m_State.vSignals.push_back(signal);
	}
	else
		m_State.vSignals.push_back(signal);
}

//
//------------------------------------------------------------------------------------------------------------------------
IFactionMap::EReactionType CAIActor::GetReaction(const IAIObject* pOtherAI, bool bUsingAIIgnorePlayer) const
{
	FUNCTION_PROFILER( gEnv->pSystem,PROFILE_AI );

	if (!pOtherAI)
		return IFactionMap::eRT_Neutral;

	if (pOtherAI == this)
		return IFactionMap::eRT_Friendly;

	if (const CAIVehicle* pThisVehicle = CastToCAIVehicle())
	{
		const CAIActor* pThisDriver = pThisVehicle->GetDriver();
		return pThisDriver ? pThisDriver->GetReaction(pOtherAI, bUsingAIIgnorePlayer) : IFactionMap::eRT_Neutral;
	}

	const CAIObject* other = static_cast<const CAIObject*>(pOtherAI);

	unsigned short int nOtherType = other->GetType();

	if (nOtherType == AIOBJECT_ATTRIBUTE)
	{
		if (CAIObject* association = other->GetAssociation().GetAIObject())
		{
			other = association;
			nOtherType = other->GetType();
		}
	}

	if (bUsingAIIgnorePlayer && (nOtherType == AIOBJECT_PLAYER) && (gAIEnv.CVars.IgnorePlayer != 0))
		return IFactionMap::eRT_Neutral;

	if (const CAIVehicle* pOtherVehicle = other->CastToCAIVehicle())
	{
		const CAIActor* pOtherDriver = pOtherVehicle->GetDriver();
		return pOtherDriver ? GetReaction(pOtherDriver, bUsingAIIgnorePlayer) : IFactionMap::eRT_Neutral;
	}

	uint8 myFaction = GetFactionID();
	uint8 otherFaction = other->GetFactionID();

	IFactionMap::EReactionType eReaction = gAIEnv.pFactionMap->GetReaction(myFaction, otherFaction);

	if ((eReaction != IFactionMap::eRT_Hostile) && !m_forcefullyHostiles.empty())
	{
		if (m_forcefullyHostiles.find(pOtherAI->GetAIObjectID()) != m_forcefullyHostiles.end())
		{
			eReaction = IFactionMap::eRT_Hostile;
		}
	}

	if (eReaction == IFactionMap::eRT_Hostile)
	{
		if (const CAIActor* actor = other->CastToCAIActor())
		{
			if (bUsingAIIgnorePlayer && (m_Parameters.m_bAiIgnoreFgNode || actor->GetParameters().m_bAiIgnoreFgNode))
			{
				eReaction = IFactionMap::eRT_Neutral;
			}
		}
	}

	return eReaction;
}


//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::Event(unsigned short eType, SAIEVENT *pEvent)
{
	CAISystem* pAISystem = GetAISystem();
	IAIActorProxy* pAIActorProxy = GetProxy();

	bool bWasEnabled = m_bEnabled;

	CAIObject::Event(eType, pEvent);

	// Inspired by CPuppet::Event's switch [4/25/2010 evgeny]
	switch (eType)
	{
	case AIEVENT_DISABLE:
		{
			// Reset and disable the agent's target track
			const tAIObjectID aiObjectId = GetAIObjectID();
			gAIEnv.pTargetTrackManager->ResetAgent(aiObjectId);
			gAIEnv.pTargetTrackManager->SetAgentEnabled(aiObjectId, false);

			pAISystem->NotifyEnableState(this, false);

			SetObserver(false);
		}
		break;
	case AIEVENT_ENABLE:
		if (pAIActorProxy->IsDead())
		{
			// can happen when rendering dead bodies? AI should not be enabled
			//				AIAssert(!"Trying to enable dead character!");
			return;
		}
		m_bEnabled = true;
		gAIEnv.pTargetTrackManager->SetAgentEnabled(GetAIObjectID(), true);
		pAISystem->NotifyEnableState(this, true);

		SetObserver(GetType() != AIOBJECT_PLAYER);
		SetObservable(true);
		break;
	case AIEVENT_SLEEP:
		m_bCheckedBody = false;
		if ( pAIActorProxy->GetLinkedVehicleEntityId() == 0 )
		{
			m_bEnabled = false;
			pAISystem->NotifyEnableState(this, m_bEnabled);
		}
		break;
	case AIEVENT_WAKEUP:
		m_bEnabled = true;
		pAISystem->NotifyEnableState(this, m_bEnabled);
		m_bCheckedBody = true;
		break;
	case AIEVENT_ONVISUALSTIMULUS:
		HandleVisualStimulus(pEvent);
		break;
	case AIEVENT_ONPATHDECISION:
		HandlePathDecision(pEvent);
		break;
	case AIEVENT_ONSOUNDEVENT:
		HandleSoundEvent(pEvent);
		break;
	case AIEVENT_ONBULLETRAIN:
		HandleBulletRain(pEvent);
		break;
	case AIEVENT_AGENTDIED:
		{
			pAISystem->NotifyTargetDead(this);

			m_bCheckedBody = false;
			m_bEnabled = false;
			pAISystem->NotifyEnableState(this, m_bEnabled);

			pAISystem->RemoveFromGroup(GetGroupId(), this);

			pAISystem->ReleaseFormationPoint(this);
			gAIEnv.pPathfinder->CancelAnyPathsFor(this);
			ReleaseFormation();

			m_State.ClearSignals();

			// Create indication of dead body.
			pAISystem->RegisterDeadBody(GetFactionID(), GetPos());

			pAISystem->OnAgentDeath(GetEntityID());

			if (pAIActorProxy)
			{
				pAIActorProxy->Reset(AIOBJRESET_SHUTDOWN);
			}

			SetObservable(false);
			SetObserver(false);
		}
		break;
	}
}

void CAIActor::EntityEvent(const SEntityEvent& event)
{
	switch (event.event)
	{
	case ENTITY_EVENT_ATTACH_THIS:
	case ENTITY_EVENT_DETACH_THIS:
		QueryBodyInfo();
	case ENTITY_EVENT_ENABLE_PHYSICS:
		UpdateObserverSkipList();
		break;
	default:
		break;
	}

	CAIObject::EntityEvent(event);
}

//====================================================================
// 
//====================================================================
bool CAIActor::CanAcquireTarget(IAIObject* pOther) const
{
	if (!pOther || !pOther->IsEnabled())
		return false;

	CCCPOINT(CAIActor_CanAcquireTarget);

	CAIObject* pOtherAI = (CAIObject*)pOther;
	if (pOtherAI->GetType() == AIOBJECT_ATTRIBUTE && pOtherAI->GetAssociation().IsValid())
		pOtherAI = (CAIObject*)pOtherAI->GetAssociation().GetAIObject();

	if (!pOtherAI || !pOtherAI->IsTargetable())
		return false;

	CAIActor* pOtherActor = pOtherAI->CastToCAIActor();
	if (!pOtherActor)
		return (pOtherAI->GetType() == AIOBJECT_TARGET);
	
	if (GetAISystem()->GetCombatClassScale(m_Parameters.m_CombatClass, pOtherActor->GetParameters().m_CombatClass )>0)
		return true;
	return false;
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::SetGroupId(int id)
{
	if (id != GetGroupId())
	{
		gAIEnv.pGroupManager->RemoveGroupMember(GetGroupId(), GetAIObjectID());
		if (id > 0)
			gAIEnv.pGroupManager->AddGroupMember(id, GetAIObjectID());

		CAIObject::SetGroupId(id);
		GetAISystem()->AddToGroup(this);	

		CAIObject* pBeacon = (CAIObject*)GetAISystem()->GetBeacon(id);
		if (pBeacon)
			GetAISystem()->UpdateBeacon(id, pBeacon->GetPos(), this);

		m_Parameters.m_nGroup = id;
	}
}

void CAIActor::SetFactionID(uint8 factionID)
{
	CAIObject::SetFactionID(factionID);

	if (IsObserver())
	{
		ObserverParams params;
		params.factionMask = GetFactionVisionMask(GetFactionID());

		gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), params, eChangedFaction);
	}
}

void CAIActor::RegisterBehaviorListener(IActorBehaviorListener* listener)
{
	m_behaviorListeners.insert(listener);
}

void CAIActor::UnregisterBehaviorListener(IActorBehaviorListener* listener)
{
	m_behaviorListeners.erase(listener);
}

void CAIActor::BehaviorEvent(EBehaviorEvent event)
{
	BehaviorListeners::iterator it = m_behaviorListeners.begin();
	BehaviorListeners::iterator end = m_behaviorListeners.end();

	for ( ; it != end; )
	{
		BehaviorListeners::iterator next = it;
		++next;

		IActorBehaviorListener* listener = *it;
		listener->BehaviorEvent(this, event);

		it = next;
	}
}

void CAIActor::BehaviorChanged(const char* current, const char* previous)
{
	BehaviorListeners::iterator it = m_behaviorListeners.begin();
	BehaviorListeners::iterator end = m_behaviorListeners.end();

	for ( ; it != end; )
	{
		BehaviorListeners::iterator next = it;
		++next;

		IActorBehaviorListener* listener = *it;
		listener->BehaviorChanged(this, current, previous);

		it = next;
	}
}


//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::SetParameters(const AgentParameters & sParams)
{
	SetGroupId(sParams.m_nGroup);
	SetFactionID(sParams.factionID);

	m_Parameters = sParams;
	m_Parameters.m_fAccuracy = clamp(m_Parameters.m_fAccuracy, 0.0f, 1.0f);

	GetAISystem()->AddToFaction(this, sParams.factionID);

	CacheFOVCos(sParams.m_PerceptionParams.FOVPrimary, sParams.m_PerceptionParams.FOVSecondary);
	float range = std::max<float>(sParams.m_PerceptionParams.sightRange,
		sParams.m_PerceptionParams.sightRangeVehicle);

	VisionChanged(range, m_FOVPrimaryCos, m_FOVSecondaryCos);
}

#ifdef CRYAISYSTEM_DEBUG
void CAIActor::UpdateHealthHistory()
{
	if(!GetProxy()) return;
	if(!m_healthHistory)
		m_healthHistory = new CValueHistory<float>(100, 0.1f);
	//better add float functions here
	float health = (GetProxy()->GetActorHealth() + GetProxy()->GetActorArmor());
	float maxHealth = (float)GetProxy()->GetActorMaxHealth();

	m_healthHistory->Sample(health / maxHealth, GetAISystem()->GetFrameDeltaTime());
}
#endif

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::Serialize( TSerialize ser )
{
	ser.Value("m_bCheckedBody", m_bCheckedBody);

	m_State.Serialize(ser);
	m_Parameters.Serialize(ser);
	SerializeMovementAbility(ser);

	CAIObject::Serialize(ser);

	if (ser.IsReading())
	{
		SetParameters(m_Parameters);
	}

	if (ser.IsReading())
	{
		const EntityId entityId = GetEntityID();
		assert(entityId > 0);

		// The proxy may already exist, if this actor is being created due to an entity 
		//	being prepared from the pool.
		if(!m_proxy.get())
		{
			AILogComment("CAIActor(%p) Creating AIActorProxy for serialization.", this);
			m_proxy.reset(GetAISystem()->GetActorProxyFactory()->CreateActorProxy(entityId));
		}
		
		gAIEnv.pActorLookUp->UpdateProxy(this);
	}

	assert(m_proxy != 0);
	if (m_proxy)
		m_proxy->Serialize(ser);
	else
		AIWarning("CAIActor::Serialize Missing proxy for \'%s\' after loading", GetName());

	assert((m_behaviorSelectionTree.get() != NULL) == (m_behaviorSelectionVariables.get() != NULL));

	if (ser.BeginOptionalGroup("BehaviorSelectionTree", m_behaviorSelectionTree.get() != NULL))
	{
		if (ser.IsReading())
			ResetBehaviorSelectionTree(AIOBJRESET_INIT);

		assert(m_behaviorSelectionTree.get() != NULL);
		assert(m_behaviorSelectionVariables.get() != NULL);

		if (m_behaviorSelectionTree.get() != NULL)
			m_behaviorSelectionTree->Serialize(ser);
		else
			AIWarning("CAIActor::Serialize Missing Behavior Selection Tree for \'%s\' after loading", GetName());

		if (m_behaviorSelectionVariables.get() != NULL)
			m_behaviorSelectionVariables->Serialize(ser);
		else
			AIWarning("CAIActor::Serialize Missing Behavior Selection Variables for \'%s\' after loading", GetName());

		if (ser.IsReading())
			UpdateBehaviorSelectionTree();

		ser.EndGroup();
	}
	else if (ser.IsReading())
	{
		m_behaviorSelectionTree.reset();
		m_behaviorSelectionVariables.reset();
	}

	bool observer = m_observer;
	ser.Value("m_observer", observer);

	if(ser.IsReading())
	{
		SetObserver(observer);

		// only alive puppets or leaders should be added to groups
		IAIActorProxy* pAIActorProxy = GetProxy();
		if (pAIActorProxy && !pAIActorProxy->IsDead())
		{
			GetAISystem()->AddToGroup(this);
		}

		ReactionChanged(0, IFactionMap::eRT_Hostile);

		m_probableTargets.clear();
		m_usingCombatLight = false;
		m_lightLevel = AILL_LIGHT;
	}

	ser.Value("maxPuppetExposure", m_detectionMax.puppetExposure);
	ser.Value("maxPuppetThreat", m_detectionMax.puppetThreat);
	ser.Value("maxVehicleExposure", m_detectionMax.vehicleExposure);
	ser.Value("maxVehicleThreat", m_detectionMax.vehicleThreat);

	ser.Value("snapshotPuppetExposure", m_detectionSnapshot.puppetExposure);
	ser.Value("snapshotPuppetThreat", m_detectionSnapshot.puppetThreat);
	ser.Value("snapshotVehicleExposure", m_detectionSnapshot.vehicleExposure);
	ser.Value("snapshotVehicleThreat", m_detectionSnapshot.vehicleThreat);

	ser.Value("m_cachedWaterOcclusionValue", m_cachedWaterOcclusionValue);

	ser.Value("m_vLastFullUpdatePos", m_vLastFullUpdatePos);

	m_refAttentionTarget.Serialize(ser, "m_refAttentionTarget");

	ser.Value("m_bCloseContact", m_bCloseContact);

	// Territory
	ser.Value("m_territoryShapeName", m_territoryShapeName);
	if(ser.IsReading())
		m_territoryShape = GetAISystem()->GetGenericShapeOfName(m_territoryShapeName.c_str());

	ser.Value("m_forcefullyHostiles", m_forcefullyHostiles);

	ser.Value("m_activeCoordinationCount", m_activeCoordinationCount);

	uint32 navigationTypeId = m_navigationTypeID;
	ser.Value("m_navigationTypeID", navigationTypeId);
	if (ser.IsReading())
	{
		m_navigationTypeID = NavigationAgentTypeID(navigationTypeId);
	}

#ifdef CRYAISYSTEM_DEBUG
	if (ser.IsReading())
	{
		ResetRecorderUnit();
	}
#endif //CRYAISYSTEM_DEBUG
}

void CAIActor::PostSerialize()
{
	CAIObject::PostSerialize();
	QueryBodyInfo();
	if (m_bodyInfo.linkedVehicleEntity)
	{
		UpdateObserverSkipList();
	}
}

void CAIActor::SetAttentionTarget(CWeakRef<CAIObject> refTarget)
{
	CCCPOINT(CAIActor_SetAttentionTarget);
	CAIObject* pAttTarget = refTarget.GetAIObject();

	m_refAttentionTarget = refTarget;

#ifdef CRYAISYSTEM_DEBUG
	SAIDebugEventData recorderEventData(pAttTarget ? pAttTarget->GetName() : "<none>");
	RecordAIDebugEvent(eAIDE_AttentionTarget, &recorderEventData);
#endif
}


Vec3 CAIActor::GetFloorPosition(const Vec3& pos)
{
	Vec3 floorPos = pos;
	return (GetFloorPos(floorPos, pos, WalkabilityFloorUpDist, WalkabilityFloorDownDist, WalkabilityDownRadius, AICE_STATIC))
		? floorPos : pos;
}


void CAIActor::CheckCloseContact(IAIObject* pTarget, float distSq) 
{
	FUNCTION_PROFILER( gEnv->pSystem,PROFILE_AI );
	if (!m_bCloseContact && distSq < sqr(GetParameters().m_fMeleeRange))
	{
		SetSignal(1, "OnCloseContact", pTarget->GetEntity(), 0, gAIEnv.SignalCRCs.m_nOnCloseContact);		
		SetCloseContact(true);
	}
}


void CAIActor::SetCloseContact(bool bCloseContact) 
{
	if (bCloseContact && !m_bCloseContact)
		m_CloseContactTime = GetAISystem()->GetFrameStartTime();
	m_bCloseContact = bCloseContact;
}

IAIObject::EFieldOfViewResult CAIActor::IsObjectInFOV(CAIObject* pTarget, float fDistanceScale) const
{
	CCCPOINT(CAIActor_IsObjectInFOVCone);
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	const Vec3& vTargetPos = pTarget->GetPos();
	const float fSightRange = GetMaxTargetVisibleRange(pTarget) * fDistanceScale;
	return (fSightRange > 0.0f ? CheckPointInFOV(vTargetPos, fSightRange) : eFOV_Outside);
}

uint32 CAIActor::GetFactionVisionMask(uint8 factionID) const
{
	uint32 mask = 0;
	uint32 factionCount = gAIEnv.pFactionMap->GetFactionCount();

	for (uint32 i = 0; i < factionCount; ++i)
	{
		if (i != factionID)
		{
			if (gAIEnv.pFactionMap->GetReaction(factionID, i) < IFactionMap::eRT_Neutral)
				mask |= 1 << i;				
		}
	}

	PersonallyHostiles::const_iterator it = m_forcefullyHostiles.begin();
	PersonallyHostiles::const_iterator end = m_forcefullyHostiles.end();

	for ( ; it != end; ++it)
	{
		if (const CAIObject* aiObject = static_cast<const CAIObject*>(GetAISystem()->GetAIObjectManager()->GetAIObject(*it)))
			if (aiObject->GetFactionID() != IFactionMap::InvalidFactionID)
				mask |= 1 << aiObject->GetFactionID();
	}

	return mask;
}

void CAIActor::SerializeMovementAbility(TSerialize ser)
{
	ser.BeginGroup("AgentMovementAbility");
	AgentMovementAbility &moveAbil = m_movementAbility;

	ser.Value("b3DMove",moveAbil.b3DMove);
	ser.Value("bUsePathfinder",moveAbil.bUsePathfinder);
	ser.Value("usePredictiveFollowing",moveAbil.usePredictiveFollowing);
	ser.Value("allowEntityClampingByAnimation",moveAbil.allowEntityClampingByAnimation);
	ser.Value("maxAccel",moveAbil.maxAccel);
	ser.Value("maxDecel",moveAbil.maxDecel);
	ser.Value("minTurnRadius",moveAbil.minTurnRadius);
	ser.Value("maxTurnRadius",moveAbil.maxTurnRadius);
	ser.Value("avoidanceRadius",moveAbil.avoidanceRadius);
	ser.Value("pathLookAhead",moveAbil.pathLookAhead);
	ser.Value("pathRadius",moveAbil.pathRadius);
	ser.Value("pathSpeedLookAheadPerSpeed",moveAbil.pathSpeedLookAheadPerSpeed);
	ser.Value("cornerSlowDown",moveAbil.cornerSlowDown);
	ser.Value("slopeSlowDown",moveAbil.slopeSlowDown);
	ser.Value("optimalFlightHeight",moveAbil.optimalFlightHeight);
	ser.Value("minFlightHeight",moveAbil.minFlightHeight);
	ser.Value("maxFlightHeight",moveAbil.maxFlightHeight);
	ser.Value("maneuverTrh",moveAbil.maneuverTrh);
	ser.Value("velDecay",moveAbil.velDecay);
	ser.Value("pathFindPrediction",moveAbil.pathFindPrediction);
	ser.Value("pathRegenIntervalDuringTrace",moveAbil.pathRegenIntervalDuringTrace);
	ser.Value("teleportEnabled",moveAbil.teleportEnabled);
	ser.Value("lightAffectsSpeed",moveAbil.lightAffectsSpeed);
	ser.Value("resolveStickingInTrace",moveAbil.resolveStickingInTrace);
	ser.Value("directionalScaleRefSpeedMin",moveAbil.directionalScaleRefSpeedMin);
	ser.Value("directionalScaleRefSpeedMax",moveAbil.directionalScaleRefSpeedMax);
	ser.Value("avoidanceAbilities",moveAbil.avoidanceAbilities);
	ser.Value("pushableObstacleWeakAvoidance",moveAbil.pushableObstacleWeakAvoidance);
	ser.Value("pushableObstacleAvoidanceRadius",moveAbil.pushableObstacleAvoidanceRadius);
	ser.Value("pushableObstacleMassMin",moveAbil.pushableObstacleMassMin);
	ser.Value("pushableObstacleMassMax",moveAbil.pushableObstacleMassMax);


	ser.BeginGroup("AgentMovementSpeeds");

	AgentMovementSpeeds &moveSpeeds = m_movementAbility.movementSpeeds;
	for (int i=0; i<AgentMovementSpeeds::AMU_NUM_VALUES; i++)
		for (int j=0; j<AgentMovementSpeeds::AMS_NUM_VALUES; j++)
		{
			ser.BeginGroup("range");
			AgentMovementSpeeds::SSpeedRange &range = moveSpeeds.GetRange(j,i);
			ser.Value("def", range.def);
			ser.Value("min", range.min);
			ser.Value("max", range.max);
			ser.EndGroup();
		}

	ser.EndGroup();

	ser.BeginGroup("AgentPathfindingProperties");
	AgentPathfindingProperties &pfProp = m_movementAbility.pathfindingProperties;
	
	ser.Value("navCapMask", pfProp.navCapMask);

	ser.Value("triangularResistanceFactor",pfProp.triangularResistanceFactor);
	ser.Value("waypointResistanceFactor",pfProp.waypointResistanceFactor);
	ser.Value("flightResistanceFactor",pfProp.flightResistanceFactor);
	ser.Value("volumeResistanceFactor",pfProp.volumeResistanceFactor);
	ser.Value("roadResistanceFactor",pfProp.roadResistanceFactor);

	ser.Value("waterResistanceFactor",pfProp.waterResistanceFactor);
	ser.Value("maxWaterDepth",pfProp.maxWaterDepth);
	ser.Value("minWaterDepth",pfProp.minWaterDepth);
	ser.Value("exposureFactor",pfProp.exposureFactor);
	ser.Value("dangerCost",pfProp.dangerCost);
	ser.Value("zScale",pfProp.zScale);

	ser.EndGroup();

	ser.EndGroup();
}


//
//------------------------------------------------------------------------------------------------------------------------
float CAIActor::AdjustTargetVisibleRange(const CAIActor& observer, float fVisibleRange) const
{
	return fVisibleRange;
}

//
//------------------------------------------------------------------------------------------------------------------------
float CAIActor::GetMaxTargetVisibleRange(const IAIObject* pTarget, bool bCheckCloak) const
{
	float fRange = 0.0f;

	// Check if I'm invisible from the observer's position
	const CAIActor *pTargetActor = CastToCAIActorSafe(pTarget);
	if (!pTargetActor || !pTargetActor->IsInvisibleFrom(GetPos(), bCheckCloak))
	{
		const AgentParameters &parameters = GetParameters();

		fRange = parameters.m_PerceptionParams.sightRange;

		if (pTarget)
		{
			// Use correct range for vehicles
			if (static_cast<const CAIObject*>(pTarget)->GetType() == AIOBJECT_VEHICLE && parameters.m_PerceptionParams.sightRangeVehicle > FLT_EPSILON)
			{
				fRange = parameters.m_PerceptionParams.sightRangeVehicle;
			}

			// Allow target to adjust the visible range as needed. This allows effects like
			//	underwater occlusion or light levels to take effect.
			if (pTargetActor && fRange > FLT_EPSILON)
			{
				fRange = pTargetActor->AdjustTargetVisibleRange(*this, fRange);
			}
		}
	}

	return fRange;
}


//
//------------------------------------------------------------------------------------------------------------------------
float CAIActor::GetCloakMaxDist( ) const 
{
	// Marcio: Reintroduced cloakScale in the max distance calculation
	float cloakMaxD( gAIEnv.CVars.CloakMaxDist );
	cloakMaxD = cloakMaxD + (m_Parameters.m_PerceptionParams.sightRange-cloakMaxD)*(1.f-m_Parameters.m_fCloakScale);
	return cloakMaxD;
}


//
//------------------------------------------------------------------------------------------------------------------------
float CAIActor::GetCloakMinDist( ) const
{
	float cloakMinD( gAIEnv.CVars.CloakMinDist );
	//	cloakMinD = cloakMinD + (m_Parameters.m_PerceptionParams.sightRange-cloakMinD)*(1.f-m_Parameters.m_fCloakScale);
	return cloakMinD;
}

//
//------------------------------------------------------------------------------------------------------------------------
bool CAIActor::IsCloakEffective(const Vec3& pos) const
{
	return (m_Parameters.m_fCloakScale > 0.f && 
		!IsUsingCombatLight() && 
		(!GetGrabbedEntity() || !IsGrabbedEntityInView(pos)) &&
		!GetBodyInfo().linkedVehicleEntity);
}


//
//------------------------------------------------------------------------------------------------------------------------
bool CAIActor::IsInvisibleFrom(const Vec3& pos, bool bCheckCloak) const 
{
	bool bInvisible = (m_Parameters.m_bInvisible);

	if (!bInvisible && bCheckCloak)
	{
		bInvisible = (m_Parameters.m_bCloaked && IsCloakEffective(pos));

		// Cloaked targets can still be seen if they are close enough to the point
		if (bInvisible)
		{
			const float cloakMaxDist = GetCloakMaxDist();
			bInvisible = Distance::Point_PointSq(GetPos(), pos) > sqr(cloakMaxDist);
		}
	}

	return bInvisible;
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::NotifyDeath()
{
}

//
//------------------------------------------------------------------------------------------------------------------------
static void CheckAndAddPhysEntity(PhysSkipList& skipList, IPhysicalEntity* physics)
{
	if (physics)
	{
		pe_status_pos	stat;

		if ((physics->GetStatus(&stat) != 0) && (((1 << stat.iSimClass) & COVER_OBJECT_TYPES) != 0))
		{
			stl::push_back_unique(skipList, physics);
		}
	}
}

//
//------------------------------------------------------------------------------------------------------------------------
void CAIActor::GetPhysicalSkipEntities(PhysSkipList& skipList) const
{
	CAIObject::GetPhysicalSkipEntities(skipList);

	const SAIBodyInfo& bi = GetBodyInfo();
	if (bi.linkedVehicleEntity)
	{
		CheckAndAddPhysEntity(skipList, bi.linkedVehicleEntity->GetPhysics());
	}

	// if holding something in hands - skip it for the vis check
	IEntity *pGrabbedEntity(GetGrabbedEntity());
	if(pGrabbedEntity)
		CheckAndAddPhysEntity(skipList, pGrabbedEntity->GetPhysics());

	// (Kevin) Adding children in most cases causes us to add too many entities to the skip list (i.e., vehicles)
	// If this is needed, investigate how we can keep this under 5 skippable entities.
	/*if (IEntity* entity = GetEntity())
	{
		for (int i = 0, ni = entity->GetChildCount(); i < ni; ++i)
		{
			if (IEntity* child = entity->GetChild(i))
				CheckAndAddPhysEntity(skipList, child->GetPhysics());
		}
	}*/

	CRY_ASSERT_MESSAGE(skipList.size() <= 5, "Too many physical skipped entities determined. See SRwiRequest definition.");
}

void CAIActor::UpdateObserverSkipList()
{
	if (m_observer)
	{
		PhysSkipList skipList;
		GetPhysicalSkipEntities(skipList);

		assert(skipList.size() <= ObserverParams::MaxSkipListSize);

		ObserverParams observerParams;
		observerParams.skipListSize = std::min<size_t>(skipList.size(), ObserverParams::MaxSkipListSize);
		for (size_t i = 0; i < static_cast<size_t>(observerParams.skipListSize); ++i)
			observerParams.skipList[i] = skipList[i];

		gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedSkipList);
	}
}

void CAIActor::GetLocalBounds(AABB& bbox) const
{
	bbox.min.zero();
	bbox.max.zero();

	IEntity* pEntity = GetEntity();
	IPhysicalEntity* pPhysicalEntity = pEntity->GetPhysics();
	if (pPhysicalEntity)
	{
		pe_status_pos pstate;
		if (pPhysicalEntity && pPhysicalEntity->GetStatus(&pstate))
		{
			bbox.min = pstate.BBox[0] / pstate.scale;
			bbox.max = pstate.BBox[1] / pstate.scale;
		}
	}
	else
	{
		return pEntity->GetLocalBounds(bbox);
	}
}

IEntity *CAIActor::GetPathAgentEntity() const
{
	return GetEntity();
}


const char *CAIActor::GetPathAgentName() const
{
	return GetName();
}


unsigned short CAIActor::GetPathAgentType() const
{
	return GetType();
}

float CAIActor::GetPathAgentPassRadius() const
{
	return GetParameters().m_fPassRadius;
}


Vec3 CAIActor::GetPathAgentPos() const
{
	return GetPhysicsPos();
}


Vec3 CAIActor::GetPathAgentVelocity() const
{
	return GetVelocity();
}

void CAIActor::GetPathAgentNavigationBlockers(TNavigationBlockers &navigationBlockers, const struct PathfindRequest *pRequest)
{

}

size_t CAIActor::GetNavNodeIndex() const
{
	if (m_lastNavNodeIndex)
		return (m_lastNavNodeIndex < ~0ul) ? m_lastNavNodeIndex : 0;

	size_t nodeIndex = gAIEnv.pGraph->GetEnclosing(GetPhysicsPos(), m_movementAbility.pathfindingProperties.navCapMask,
		m_Parameters.m_fPassRadius,	0, 0.0f, NULL, false, GetName());

	m_lastNavNodeIndex = nodeIndex ? nodeIndex : ~0ul;

	return nodeIndex;
}

const AgentMovementAbility &CAIActor::GetPathAgentMovementAbility() const
{
	return m_movementAbility;
}

void CAIActor::PathEvent(SAIEVENT *pEvent)
{
	Event(AIEVENT_ONPATHDECISION, pEvent);
}

unsigned int CAIActor::GetPathAgentLastNavNode() const
{
	return GetNavNodeIndex();
}

void CAIActor::SetPathAgentLastNavNode(unsigned int lastNavNode)
{
	m_lastNavNodeIndex=lastNavNode;
}

void CAIActor::SetPathToFollow( const char *pathName )
{

}

void CAIActor::SetPathAttributeToFollow( bool bSpline )
{

}

void CAIActor::SetPFBlockerRadius( int blockerType, float radius )
{

}

ETriState CAIActor::CanTargetPointBeReached( CTargetPointRequest &request )
{
	request.SetResult(eTS_false); return eTS_false;
}

bool CAIActor::UseTargetPointRequest( const CTargetPointRequest &request )
{
	return false;
}

IPathFollower* CAIActor::GetPathFollower() const
{
	return NULL;
}

bool CAIActor::GetValidPositionNearby( const Vec3 &proposedPosition, Vec3 &adjustedPosition ) const
{
	return false;
}

bool CAIActor::GetTeleportPosition( Vec3 &teleportPos ) const
{
	return false;
}

//===================================================================
// GetSightFOVCos
//===================================================================
void CAIActor::GetSightFOVCos(float &fPrimaryFOVCos, float &fSecondaryFOVCos) const
{
	fPrimaryFOVCos = m_FOVPrimaryCos;
	fSecondaryFOVCos = m_FOVSecondaryCos;
}

//===================================================================
// TransformFOV
//===================================================================
void CAIActor::CacheFOVCos(float FOVPrimary, float FOVSecondary)
{
	if (FOVPrimary < 0.0f || FOVPrimary > 360.0f )	// see all around
	{
		m_FOVPrimaryCos = -1.0f;
		m_FOVSecondaryCos = -1.0f;
	}
	else 
	{
		if( FOVSecondary >= 0.0f && FOVPrimary > FOVSecondary )
			FOVSecondary = FOVPrimary;

		m_FOVPrimaryCos = cosf( DEG2RAD( FOVPrimary * 0.5f ) );

		if (FOVSecondary < 0.0f || FOVSecondary > 360.0f )	// see all around
			m_FOVSecondaryCos = -1.0f;
		else 
			m_FOVSecondaryCos = cosf( DEG2RAD( FOVSecondary * 0.5f ) );
	}
}

void CAIActor::ReactionChanged(uint8 factionID, IFactionMap::EReactionType reaction)
{
	if (m_observer)
	{
		ObserverParams params;
		params.factionMask = GetFactionVisionMask(GetFactionID());

		gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), params, eChangedFaction);
	}
}

void CAIActor::VisionChanged(float sightRange, float primaryFOVCos, float secondaryFOVCos)
{
	if (m_observer)
	{
		ObserverParams observerParams;
		observerParams.sightRange = sightRange;
		observerParams.primaryFoVCos = primaryFOVCos;
		observerParams.peripheralFoVCos = secondaryFOVCos;

		gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedSight);
	}
}

void CAIActor::SetObserver(bool observer)
{
	if (m_observer != observer)
	{
		if (observer)
		{
			ObserverParams observerParams;
			observerParams.entityID = GetEntityID();
			observerParams.factionMask = GetFactionVisionMask(GetFactionID());
			observerParams.typeMask = GetObserverTypeMask();
			observerParams.eyePos = GetPos();
			observerParams.eyeDir = GetViewDir();
			observerParams.priority = eMediumPriority;
			observerParams.sightRange = m_Parameters.m_PerceptionParams.sightRange;

			PhysSkipList skipList;
			GetPhysicalSkipEntities(skipList);

			observerParams.skipListSize = std::min<size_t>(skipList.size(), ObserverParams::MaxSkipListSize);
			for (size_t i = 0; i < static_cast<size_t>(observerParams.skipListSize); ++i)
				observerParams.skipList[i] = skipList[i];

			GetSightFOVCos(observerParams.primaryFoVCos, observerParams.peripheralFoVCos);

			VisionID visionID = GetVisionID();
			if (!visionID)
			{
				visionID = gAIEnv.pVisionMap->CreateVisionID(GetName());

				SetVisionID(visionID);
			}

			gAIEnv.pVisionMap->RegisterObserver(visionID, observerParams);
		}
		else
		{
			if (VisionID visionID = GetVisionID())
				gAIEnv.pVisionMap->UnregisterObserver(visionID);
		}

		m_observer = observer;
	}
}

uint32 CAIActor::GetObserverTypeMask() const
{
	return General | AliveAgent | DeadAgent | Player;
}

uint32 CAIActor::GetObservableTypeMask() const
{
	return General | AliveAgent;
}

bool CAIActor::IsObserver() const
{
	return m_observer;
}

bool CAIActor::CanSee(const VisionID& otherID) const
{
	return gEnv->pAISystem->GetVisionMap()->IsVisible(GetVisionID(), otherID);
}

void CAIActor::AddPersonallyHostile(tAIObjectID hostileID)
{
	m_forcefullyHostiles.insert(hostileID);

	ReactionChanged(0, IFactionMap::eRT_Hostile);
}

void CAIActor::RemovePersonallyHostile(tAIObjectID hostileID)
{
	m_forcefullyHostiles.erase(hostileID);

	ReactionChanged(0, IFactionMap::eRT_Hostile);
}

void CAIActor::ResetPersonallyHostiles()
{
	m_forcefullyHostiles.clear();

	ReactionChanged(0, IFactionMap::eRT_Hostile);
}

bool CAIActor::IsPersonallyHostile(tAIObjectID hostileID) const
{
	return m_forcefullyHostiles.find(hostileID) != m_forcefullyHostiles.end();
}

void CAIActor::SetProxy(IAIActorProxy* proxy)
{
	m_proxy.reset(proxy);
}

IAIActorProxy* CAIActor::GetProxy() const
{
	return m_proxy;
}

void CAIActor::ClearProbableTargets()
{
	m_probableTargets.clear();
}

void CAIActor::AddProbableTarget( CAIObject* pTarget )
{
	m_probableTargets.push_back(pTarget);
}

IAIObject::EFieldOfViewResult CAIActor::CheckPointInFOV(const Vec3& vPoint, float fSightRange) const
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	EFieldOfViewResult eResult = eFOV_Outside;
	Vec3 vDirection = vPoint - GetPos();
	const float fDirectionLengthSq = vDirection.GetLengthSquared();

	// lets see if it is outside of its vision range
	if (fDirectionLengthSq <= sqr(fSightRange))
	{
		if (fDirectionLengthSq > 0.1f)
		{
			float primaryFOVCos;
			float secondaryFOVCos;
			GetSightFOVCos(primaryFOVCos, secondaryFOVCos);

			vDirection.Normalize();
			const Vec3 vViewDir = GetViewDir();
			const float fDot = vDirection.Dot(vViewDir);

			// Check for the omni-directional special case.
			if (secondaryFOVCos <= -1.f || fDot >= secondaryFOVCos)
			{
				const bool bInPrimaryFOV = (primaryFOVCos <= -1.f || fDot >= primaryFOVCos);
				eResult = bInPrimaryFOV ? eFOV_Primary : eFOV_Secondary;
			}
		}
		else
		{
			eResult = eFOV_Secondary;
		}
	}

	return eResult;
}

void CAIActor::HandlePathDecision(SAIEVENT* pAIEvent)
{
}

void CAIActor::HandleVisualStimulus(SAIEVENT* pAIEvent)
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	const float fVisualPerceptionScale = m_Parameters.m_PerceptionParams.perceptionScale.visual;
	if (gAIEnv.CVars.IgnoreVisualStimulus != 0 || m_Parameters.m_bAiIgnoreFgNode || fVisualPerceptionScale <= 0.0f)
		return;

	if (gAIEnv.pTargetTrackManager->IsEnabled())
	{
		// Check if in range (using perception scale)
		if (eFOV_Outside != IsPointInFOV(pAIEvent->vPosition, fVisualPerceptionScale))
		{
			gAIEnv.pTargetTrackManager->HandleStimulusEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_Visual);
	
			IEntity* pEventOwnerEntity = gEnv->pEntitySystem->GetEntity(pAIEvent->sourceId);
			if (!pEventOwnerEntity)
				return;

			IAIObject* pEventOwnerAI = pEventOwnerEntity->GetAI();
			if (!pEventOwnerAI)
				return;

			if (IsHostile(pEventOwnerAI))
			{
				m_State.nTargetType = static_cast<CAIObject*>(pEventOwnerAI)->GetType();
				m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds();

				m_State.eTargetThreat = AITHREAT_AGGRESSIVE;
				m_State.eTargetType = AITARGET_VISUAL;
				
				CWeakRef<CAIObject> refAttentionTarget = GetWeakRef(static_cast<CAIObject*>(pEventOwnerAI));
				if (refAttentionTarget != m_refAttentionTarget)
				{
					SetAttentionTarget(refAttentionTarget);
				}
			}
		}
	}
}

void CAIActor::HandleSoundEvent(SAIEVENT* pAIEvent)
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	const float fAudioPerceptionScale = m_Parameters.m_PerceptionParams.perceptionScale.audio;
	if (gAIEnv.CVars.IgnoreSoundStimulus != 0 || m_Parameters.m_bAiIgnoreFgNode || fAudioPerceptionScale <= 0.0f)
		return;

	if (gAIEnv.pTargetTrackManager->IsEnabled())
	{
		// Check if in range (using perception scale)
		const Vec3 &vMyPos = GetPos();
		const float fSoundDistance = vMyPos.GetDistance(pAIEvent->vPosition) * (1.0f / fAudioPerceptionScale);
		if (fSoundDistance <= pAIEvent->fThreat)
		{
			gAIEnv.pTargetTrackManager->HandleStimulusEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_Sound);
	
			IEntity* pEventOwnerEntity = gEnv->pEntitySystem->GetEntity(pAIEvent->sourceId);
			if (!pEventOwnerEntity)
				return;

			IAIObject* pEventOwnerAI = pEventOwnerEntity->GetAI();
			if (!pEventOwnerAI)
				return;

			if (IsHostile(pEventOwnerAI))
			{
				if ((m_State.eTargetType != AITARGET_MEMORY) && (m_State.eTargetType != AITARGET_VISUAL))
				{
					m_State.nTargetType = static_cast<CAIObject*>(pEventOwnerAI)->GetType();
					m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds();

					m_State.nTargetType = static_cast<CAIObject*>(pEventOwnerAI)->GetType();
					m_State.eTargetThreat = AITHREAT_AGGRESSIVE;
					m_State.eTargetType = AITARGET_SOUND;

					SetAttentionTarget(GetWeakRef(static_cast<CAIObject*>(pEventOwnerAI)));
				}
			}
		}
	}
}

void CAIActor::HandleBulletRain(SAIEVENT* pAIEvent)
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	if (gAIEnv.CVars.IgnoreBulletRainStimulus || m_Parameters.m_bAiIgnoreFgNode)
		return;

	IAISignalExtraData* pData = GetAISystem()->CreateSignalExtraData();
	pData->point = pAIEvent->vPosition;
	pData->point2 = pAIEvent->vStimPos;
	pData->nID = pAIEvent->sourceId;
	pData->fValue = pAIEvent->fThreat; // pressureMultiplier

	SetSignal(0, "OnBulletRain", GetEntity(), pData, gAIEnv.SignalCRCs.m_nOnBulletRain);

	if (gAIEnv.pTargetTrackManager->IsEnabled())
		gAIEnv.pTargetTrackManager->HandleStimulusEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_BulletRain);
}

void CAIActor::CancelRequestedPath(bool actorRemoved)
{

}

IAIObject::EFieldOfViewResult CAIActor::IsPointInFOV(const Vec3& vPos, float fDistanceScale) const
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	const float fSightRange = m_Parameters.m_PerceptionParams.sightRange * fDistanceScale;
	return CheckPointInFOV(vPos, fSightRange);
}

void CAIActor::GetMovementSpeedRange(float fUrgency, bool bSlowForStrafe, float& normalSpeed, float& minSpeed, float& maxSpeed) const
{
	AgentMovementSpeeds::EAgentMovementUrgency urgency;
	AgentMovementSpeeds::EAgentMovementStance stance;

	bool vehicle = GetType() == AIOBJECT_VEHICLE;

	if (fUrgency < 0.5f * (AISPEED_SLOW + AISPEED_WALK))
		urgency = AgentMovementSpeeds::AMU_SLOW;
	else if (fUrgency < 0.5f * (AISPEED_WALK + AISPEED_RUN))
		urgency = AgentMovementSpeeds::AMU_WALK;
	else if (fUrgency < 0.5f * (AISPEED_RUN + AISPEED_SPRINT))
		urgency = AgentMovementSpeeds::AMU_RUN;
	else
		urgency = AgentMovementSpeeds::AMU_SPRINT;
  
	if (IsAffectedByLight() && m_movementAbility.lightAffectsSpeed)
	{
		// Disable sprinting in dark light conditions.
		if (urgency == AgentMovementSpeeds::AMU_SPRINT)
		{
			EAILightLevel eAILightLevel = GetLightLevel();
			if ((eAILightLevel == AILL_DARK) || (eAILightLevel == AILL_SUPERDARK))
				urgency = AgentMovementSpeeds::AMU_RUN;
		}
	}

	const SAIBodyInfo& bodyInfo = GetBodyInfo();

	switch (bodyInfo.stance)
	{
	case STANCE_STEALTH: stance = AgentMovementSpeeds::AMS_STEALTH; break;
	case STANCE_CROUCH: stance = AgentMovementSpeeds::AMS_CROUCH; break;
	case STANCE_PRONE: stance = AgentMovementSpeeds::AMS_PRONE; break;
	case STANCE_SWIM: stance = AgentMovementSpeeds::AMS_SWIM; break;
	case STANCE_RELAXED: stance = AgentMovementSpeeds::AMS_RELAXED; break;
	case STANCE_ALERTED: stance =  AgentMovementSpeeds::AMS_ALERTED; break;
	case STANCE_LOW_COVER: stance = AgentMovementSpeeds::AMS_LOW_COVER; break;
	case STANCE_HIGH_COVER: stance = AgentMovementSpeeds::AMS_HIGH_COVER; break;
	default: stance = AgentMovementSpeeds::AMS_COMBAT; break;
	}

	const float artificialMinSpeedMult = 1.0f;

	AgentMovementSpeeds::SSpeedRange	fwdRange = m_movementAbility.movementSpeeds.GetRange(stance, urgency);
	fwdRange.min *= artificialMinSpeedMult;
	normalSpeed = fwdRange.def;
	minSpeed = fwdRange.min;
	maxSpeed = fwdRange.max;

	if (m_movementAbility.directionalScaleRefSpeedMin > 0.0f)
	{
		float desiredSpeed = normalSpeed;
		float desiredTurnSpeed = m_bodyTurningSpeed;
		float travelAngle = Ang3::CreateRadZ(GetEntityDir(), GetMoveDir()); //m_State.vMoveDir);

		const float refSpeedMin = m_movementAbility.directionalScaleRefSpeedMin;
		const float refSpeedMax = m_movementAbility.directionalScaleRefSpeedMax;

		// When a locomotion is slow (0.5-2.0f), then we can do this motion in all direction more or less at the same speed 
		float t = sqr(clamp((desiredSpeed - refSpeedMin) / (refSpeedMax - refSpeedMin), 0.0f, 1.0f));
		float scaleLimit = clamp(0.8f*(1-t) + 0.1f*t, 0.3f, 1.0f); //never scale more then 0.4 down 

		float turnSlowDownFactor = (gAIEnv.configuration.eCompatibilityMode == ECCM_CRYSIS2) ? 0.2f : 0.4f;
		//adjust desired speed for turns
		float speedScale = 1.0f - fabsf(desiredTurnSpeed*turnSlowDownFactor)/gf_PI;
		speedScale = clamp(speedScale, scaleLimit, 1.0f);

		//adjust desired speed when strafing and running backward
		float strafeSlowDown = (gf_PI - fabsf(travelAngle*0.60f))/gf_PI;
		strafeSlowDown = clamp(strafeSlowDown, scaleLimit, 1.0f);

		//adjust desired speed when running uphill & downhill
		float slopeSlowDown = (gf_PI - fabsf(DEG2RAD(bodyInfo.slopeAngle)/12.0f))/gf_PI;
		slopeSlowDown = clamp(slopeSlowDown, scaleLimit, 1.0f);

		float scale = min(speedScale, min(strafeSlowDown, slopeSlowDown));

		normalSpeed *= scale;
		Limit(normalSpeed, minSpeed, maxSpeed);
	}
}

void CAIActor::ResetLookAt()
{
	m_State.vLookTargetPos.zero();
}

bool CAIActor::SetLookAtPointPos(const Vec3& vPoint, bool bPriority)
{
	m_State.vLookTargetPos = vPoint;
	
	// Inspired by CPipeUser::SetLookAtPointPos [5/3/2010 evgeny]
	Vec3 vDesired = vPoint - GetPos();
	if (!m_movementAbility.b3DMove)
	{
		vDesired.z = 0;
	}
	vDesired.NormalizeSafe();
	
	const SAIBodyInfo& bodyInfo = GetBodyInfo();

	Vec3 vCurrent = bodyInfo.vEyeDirAnim;
	if (!m_movementAbility.b3DMove)
	{
		vCurrent.z = 0;
	}
	vCurrent.NormalizeSafe();

	// cos( 11.5deg ) ~ 0.98
	return 0.98f <= vCurrent.Dot(vDesired);
}

bool CAIActor::SetLookAtDir(const Vec3& vDir, bool bPriority)
{
	Vec3 vDirCopy = vDir;
	return vDirCopy.NormalizeSafe() ? SetLookAtPointPos(GetPos() + vDirCopy * 100.0f) : true;
}

void CAIActor::ResetBodyTargetDir()
{
	m_State.vBodyTargetDir.zero();
}

void CAIActor::SetBodyTargetDir(const Vec3& vDir)
{
	m_State.vBodyTargetDir = vDir;
}

void CAIActor::SetMoveTarget(const Vec3& vMoveTarget)
{
	m_State.vMoveTarget = vMoveTarget;
}

const Vec3& CAIActor::GetBodyTargetDir() const
{
	return m_State.vBodyTargetDir;
}

void CAIActor::GoTo(const Vec3& vTargetPos)
{
	m_State.vLookTargetPos = vTargetPos;
	m_State.vMoveTarget = vTargetPos;
}

void CAIActor::SetSpeed(float fSpeed)
{
	m_State.fMovementUrgency = fSpeed;
}

IPhysicalEntity* CAIActor::GetPhysics(bool bWantCharacterPhysics /* = false */) const
{
	IAIActorProxy* pAIActorProxy = GetProxy();
	return pAIActorProxy ? pAIActorProxy->GetPhysics(bWantCharacterPhysics)
	                     : CAIObject::GetPhysics(bWantCharacterPhysics);
}

bool CAIActor::CanDamageTarget(IAIObject* target) const
{
	return true;
}

bool CAIActor::CanDamageTargetWithMelee() const
{
	return true;
}

void CAIActor::SetTerritoryShapeName(const char* szName)
{
	assert(szName);

	if (m_territoryShapeName.compare(szName))
	{
		m_territoryShapeName = szName;

		if (m_territoryShapeName.compare("<None>"))
		{
			m_territoryShape = GetAISystem()->GetGenericShapeOfName(szName);	// i.e. m_territoryShapeName

			if (m_territoryShape)
			{
				// Territory shapes should be really simple
				size_t size = m_territoryShape->shape.size();
				if (size > 8)
				{
					AIWarning("Territory shape %s for %s has %d points.  Territories should not have more than 8 points",
						szName, GetName(), (int32)size);
				}
			}
			else
			{
				m_territoryShapeName += " (not found)";
			}
		}
		else
		{
			m_territoryShape = 0;
		}
	}
}

const char* CAIActor::GetTerritoryShapeName() const
{
	return (gEnv->IsEditor() && gEnv->IsEditing())
		? m_Parameters.m_sTerritoryName.c_str()
		: m_territoryShapeName.c_str();
}

bool CAIActor::IsPointInsideTerritoryShape(const Vec3& vPos, bool bCheckHeight) const
{
	bool bResult = true;

	const SShape *pTerritory = GetTerritoryShape();
	if (pTerritory)
	{
		bResult = pTerritory->IsPointInsideShape(vPos, bCheckHeight);
	}

	return bResult;
}

bool CAIActor::ConstrainInsideTerritoryShape(Vec3& vPos, bool bCheckHeight) const
{
	bool bResult = true;

	const SShape *pTerritory = GetTerritoryShape();
	if (pTerritory)
	{
		bResult = pTerritory->ConstrainPointInsideShape(vPos, bCheckHeight);
	}

	return bResult;
}

//===================================================================
// GetObjectType
//===================================================================
CAIActor::EAIObjectType CAIActor::GetObjectType(const CAIObject *ai, unsigned short type)
{
  if (type == AIOBJECT_PLAYER)
    return AIOT_PLAYER;
	else if (type == AIOBJECT_ACTOR)
    return AIOT_AGENTSMALL;
  else if (type == AIOBJECT_VEHICLE)
  {
    // differentiate between medium and big vehicles (e.g. tank can drive over jeep)
    return AIOT_AGENTMED;
  }
  else
    return AIOT_UNKNOWN;
}

//===================================================================
// GetNavInteraction
//===================================================================
CAIActor::ENavInteraction CAIActor::GetNavInteraction(const CAIObject *navigator, const CAIObject *obstacle)
{
	// Driver ignores obstacles - let the vehicle do this job instead
	if (const CAIActor* pAIActorNavigator = navigator->CastToCAIActor())
	{
		const SAIBodyInfo& aiBodyInfo = pAIActorNavigator->GetBodyInfo();
		if (aiBodyInfo.linkedVehicleEntity)
		{
			return NI_IGNORE;
		}
	}

	// Vehicle ignores passengers - it's their vehicles it should avoid instead
	if (const CAIActor* pAIActorObstacle = obstacle->CastToCAIActor())
	{
		const SAIBodyInfo& aiBodyInfo = pAIActorObstacle->GetBodyInfo();
		if (aiBodyInfo.linkedVehicleEntity)
		{
			return NI_IGNORE;
		}
	}

  unsigned short navigatorType = navigator->GetType();
  unsigned short obstacleType = obstacle->GetType();

	bool enemy = navigator->IsHostile(obstacle);

  EAIObjectType navigatorOT = GetObjectType(navigator, navigatorType);
  EAIObjectType obstacleOT = GetObjectType(obstacle, obstacleType);

  switch (navigatorOT)
  {
  case AIOT_UNKNOWN: 
  case AIOT_PLAYER: 
    return NI_IGNORE;
  case AIOT_AGENTSMALL: 
    // don't navigate around their enemies, unless the enemy is bigger
/*    if (enemy)
      return obstacleOT > navigatorOT ? NI_STEER : NI_IGNORE;
    else*/
      return NI_STEER;
  case AIOT_AGENTMED: 
  case AIOT_AGENTBIG: 
    // don't navigate around their enemies, unless the enemy is same size or bigger
    if (enemy)
      return obstacleOT >= navigatorOT ? NI_STEER : NI_IGNORE;
    else
      return NI_STEER;
  default:
    AIError("GetNavInteraction: Unhandled switch case %d", navigatorOT);
    return NI_IGNORE;
  }
}

//===================================================================
// GetLiveTarget
//===================================================================
CWeakRef<CAIActor> CAIActor::GetLiveTarget(const CWeakRef<CAIObject> refTarget)
{
	CCCPOINT(CPuppet_GetLiveTarget);

	CWeakRef<CAIActor> refResult;
	CAIObject* pTarget = refTarget.GetAIObject();
	if (pTarget)
	{
		CAIActor* pAIActor = pTarget->CastToCAIActor();
		if (pAIActor && pAIActor->IsActive() && pAIActor->IsAgent())
		{
			refResult = StaticCast<CAIActor>(refTarget);
		}
		else
		{
			CAIActor* pAssociatedAIActor = CastToCAIActorSafe(pTarget->GetAssociation().GetAIObject());
			if (pAssociatedAIActor && pAssociatedAIActor->IsEnabled() && pAssociatedAIActor->IsAgent())
			{
				refResult = GetWeakRef(pAssociatedAIActor);
			}
		}
	}
	return refResult;
}

const CAIObject* CAIActor::GetLiveTarget(const CAIObject* pTarget)
{
	if (!pTarget)
		return 0;
	if (const CAIActor* pTargetAIActor = pTarget->CastToCAIActor())
		if (!pTargetAIActor->IsActive())
			return 0;
	if (pTarget->IsAgent())
		return pTarget;
	const CAIObject* pAssociation = pTarget->GetAssociation().GetAIObject();
	return (pAssociation && pAssociation->IsEnabled() && pAssociation->IsAgent()) ? pAssociation : 0;
}

void CAIActor::CoordinationEntered(const char* signalName)
{
	++m_activeCoordinationCount;
	assert(m_activeCoordinationCount < 10);
	
	SetSignal(AISIGNAL_ALLOW_DUPLICATES, signalName);
}

void CAIActor::CoordinationExited(const char* signalName)
{
	//assert(m_activeCoordinationCount < 10 && m_activeCoordinationCount > 0);
	// Morgan - 10-28-10 This should not require a check against zero. 
	// If its already zero, then coordinations were stopped, that had already actors who had finished their roles.
	// Need addition to coordination system to prevent stop methods being called for already complete sequences.
	if(m_activeCoordinationCount > 0)
	{
		--m_activeCoordinationCount;
	}	

	if(m_activeCoordinationCount == 0)
		SetSignal(AISIGNAL_ALLOW_DUPLICATES, signalName);
}


bool CAIActor::HasAIRecorderUnit() const
{
#ifdef CRYAISYSTEM_DEBUG
	return (const_cast<CAIActor*>(this)->GetOrCreateRecorderUnit() != NULL);
#else
	return false;
#endif
}

void CAIActor::RecordAIDebugEvent(EAIDebugEvent eAIDebugEvent, const SAIDebugEventData* pAIDebugEventData)
{
#ifdef CRYAISYSTEM_DEBUG
	if (CRecorderUnit* pRecorderUnit = GetOrCreateRecorderUnit())
	{
		pRecorderUnit->RecordEvent(eAIDebugEvent, pAIDebugEventData);
	}
#endif
}

IAIDebugStream* CAIActor::GetAIDebugStream(EAIDebugEvent streamTag)
{
#ifdef CRYAISYSTEM_DEBUG
	if (m_pRecorderUnit)
	{
		return m_pRecorderUnit->GetAIDebugStream(streamTag);
	}
#endif
	return NULL;
}

#ifdef CRYAISYSTEM_DEBUG
CRecorderUnit* CAIActor::GetOrCreateRecorderUnit(bool bForce)
{
	if (!m_pRecorderUnit && !gEnv->pSystem->IsSerializingFile()) 
	{
		m_pRecorderUnit = gAIEnv.GetAIRecorder()->AddUnit(GetSelfReference(), bForce);
	}

	return m_pRecorderUnit;
}

void CAIActor::ResetRecorderUnit()
{
	SAFE_DELETE(m_pRecorderUnit);
}
#endif
